前言

目前TanStack有这些内容,我难道都要学习吗?很明显不是。

image-20251227065237625

以下是截至 2025年12月 TanStack 生态中这些库的主要作用和当前成熟度简要说明(基于官方文档和最新状态):

库名称成熟度主要作用 / 解决什么问题典型使用场景备注 / 推荐程度
TanStack StartRC全栈 React 应用框架(基于 Router + Vite),提供类型安全的 SSR、Streaming、Server Functions 等Next.js / Remix 替代方案,全栈 SPA/SSR 项目正在冲 1.0,适合尝鲜全栈
TanStack RouterRC → 稳定最强大、最类型安全的 React 路由器,支持文件路由、数据加载、搜索参数、上下文等中大型 SPA、全栈应用路由核心目前 TanStack 生态最成熟产品之一
TanStack Query稳定服务端状态管理 / 数据获取库(原 React Query),缓存、自动重试、乐观更新、无限滚动等API 数据获取、缓存、实时同步几乎所有 React 项目必备
TanStack TableBETA → 稳定功能强大的表组件(原 React Table),支持排序、分页、过滤、虚拟化、列拖拽等数据表格、仪表盘、复杂列表v8+ 版本非常强大,广泛使用
TanStack DBBETA响应式客户端数据库 + 集合 + 实时查询 + 乐观更新,构建在 differential dataflow 上本地优先/实时应用、大数据集即时交互2025年新星,适合追求极致性能
TanStack AIALPHA开源 AI SDK,提供统一接口支持多个 AI 提供商(无厂商锁定)集成 ChatGPT/Claude/Gemini 等 AI 服务早期阶段,关注 TypeScript 友好
TanStack FormNEW轻量、类型安全、框架无关的表单库,支持 Zod/Yup 等校验复杂表单、动态表单、多步表单较新,定位取代 Formik/React Hook Form
TanStack Virtual稳定头文件虚拟化库(原 react-virtual),高效渲染超长列表/表格/网格(60fps)大数据列表、聊天记录、虚拟滚动表格非常成熟,性能极强
TanStack PacerBETA性能优化原语集合:防抖、节流、限流、队列、批处理等输入优化、API 限流、批量更新实用工具集,解决常见性能痛点
TanStack StoreALPHA不可变 + 响应式数据存储,为 TanStack 生态核心提供动力(类似微型 zustand)全局/局部状态管理内部使用较多,外部项目较少
TanStack DevtoolsALPHA统一的 TanStack 库开发工具面板(Query/Router/Table 等 devtools 集合)调试 TanStack 生态所有库正在统一,未来会很强大

核心稳定三件套:Query + Router + Table / Virtual ,项目中用好这三个就行了。如果Start稳定了,那么就都转到Start上面去即可。

Installation

按照shadcn创建项目的步骤来创建即可:https://ui.shadcn.com/docs/installation/vite

安装依赖:pnpm add @tanstack/react-form

dev工具:

在main.tsx里面使用devtools。

image-20251227073544714

启动项目,出现dev图标即可:

image-20251227074058406

First Form

在 tanstack/React Form 中,核心是通过 useForm 创建表单实例,然后使用 form.Field 来管理具体字段。

表单创建基本模式

1. useForm 钩子:表单的大脑

这是整个表单的控制中心。

2. form.Field 组件:细粒度订阅 (Fine-grained Subscriptions)

这是 TanStack Form 性能强大的核心原因。

3. field 实例提供的核心属性

form.Field 内部,通过参数拿到的 field 对象包含了管理该字段所需的一切:

4. 声明式提交处理

示例中展示了标准的 HTML 提交拦截方式:

这种方式将 DOM 事件与表单逻辑完全解耦。

简单示例

在App.tsx里面引入这个form,查看效果,可以看到,submit的data里面,数据是正常的,打开devtools,可以看到各种表单状态。

 

注意:tanstack/react-form的devtools还没有成熟,有点bug,如果看不到表单,要么刷新页面,要么重启服务。

看上去@tanstack/react-form有点类似react hook form,确实有很多地方相同。但还是很不同的:

特性@tanstack/react-formReact Hook Form
状态管理受控模式 (Controlled) 但高性能非受控模式 (Uncontrolled) 为主
类型安全极强。深度嵌套对象的类型推导非常完美较强,但深度嵌套时类型有时会失效
学习曲线略高(函数式响应式思维)较低(更符合 HTML 标准)
校验器支持内置并完美支持 Zod, Valibot, Yup 等需配合 Resolver
框架支持React, Vue, Svelte, Solid, Angular主要是 React

Fields Meta Data

@tanstack/react-form 中,Field Meta Data(字段元数据) 是指除了字段本身的“值”之外,描述该字段当前状态的一组信息。

简单来说,如果 value 是用户输入的内容,那么 meta 就是关于这个内容及其输入过程的“体检报告”。

1. 为什么需要 Meta Data?

在一个复杂的表单中,你不仅需要知道用户填了什么,还需要知道:

2. 常见的 Meta 属性及其用途

Field Meta

当你通过 field.state.meta 访问时,最常用的属性包括:

属性类型含义典型用途
isTouchedboolean字段是否被聚焦并失焦过(Blur)。错误提示:通常只有在 isTouched 为 true 时才显示报错,防止用户还没开始填就满屏红字。
isDirtyboolean当前值是否与初始值(defaultValues)不同。状态监控:用来判断表单是否被修改,决定是否弹出“离开页面不保存”的提醒。
errorsArray包含该字段所有校验失败信息的数组。UI 展示:遍历此数组显示具体的错误文字。
isValidatingboolean异步校验是否正在进行中。加载反馈:如果设置了远程校验(如检查用户名是否重复),可用它显示微小的 Spinner。
isPristineboolean字段是否从未被修改过。isDirty 相反。

Form Meta

通过 form.useStore((s) => s.meta)<form.Subscribe />。常用属性:

isSubmitting: 整个表单是否正在提交过程中。常用于禁用提交按钮并显示 Spinner。

isValid: 表单内所有字段是否都通过了校验。

isDirty: 表单中是否有任何一个字段被修改过。用于控制“重置”按钮或离开页面提醒。

isTouched: 表单中是否有任何一个字段被触碰过。

canSubmit: 一个综合状态,通常表示 !isSubmitting && isValid

该用哪一个?

3. 在代码中如何使用?

通过你在上一条信息中提到的 field 对象,你可以非常灵活地控制 UI 的展现逻辑。

4. Meta Data 的更新机制

总结

Meta Data 是表单的“上下文信息”。没有它,你只能做一个能提交的表单;有了它,你才能做一个拥有完美用户体验(例如:按需报错、提交禁用、异步加载反馈)的高级表单。

演示

当field变化时,devtools里面可以看到meta相关属性都变化了。而且form相关的meta也变化了。

Reactivity & Listeners

Reactivity

在 UI 开发(尤其是 React 和 TanStack 系列)中,Reactivity(响应式) 是指一种“数据驱动 UI 自动更新”的机制。

简单来说:当数据(State)发生变化时,所有依赖该数据的界面部分(UI)应当能够“自动”且“高效”地同步更新,而不需要你手动去操作 DOM。

听上去类似于vue的数据双向绑定,但二者是有区别的,最显著的区别就是tanstack form里面需要显式的订阅来实现响应式。

谈到 Reactivity(响应式),Vue 是这个领域的“鼻祖”级代表。虽然 TanStack Form (React) 和 Vue 都追求“数据变了 UI 跟着变”,但它们的底层实现逻辑开发者的心智模型有本质区别。

1. 实现机制的差异:Proxy vs. Hooks

Vue: 自动追踪 (Mutable & Transparent)

TanStack Form (React): 显式订阅 (Immutable & Hook-based)

2. 渲染精度的差异:组件级 vs. 节点级

3. 心智模型:谁更“黑盒”?

特性Vue (Reactivity)TanStack Form (React)
透明度。你看不见订阅过程,Vue 帮你做了。。你需要理解“订阅”和“选择器(Selector)”。
依赖声明自动。你在模板里用了,它就自动收集依赖。手动。你需要通过 name="xxx"selector 声明。
可预测性极佳。但有时候难以追踪是谁改了数据(特别是嵌套深时)。极佳。所有状态流向都是显式的,Debugger 好追溯。

4. 为什么 TanStack Form 在 React 里显得很像 Vue?

如果你觉得 TanStack Form 用起来有种“Vue 感”,那是因为它在努力克服 React 的缺点

  1. React 默认是“牵一发而动全身”(父组件变了子组件跟着变)。
  2. TanStack Form 通过内部的 Store 避开了 React 的全量渲染。
  3. 它让 React 开发者也能享受到 Vue 那种“改个字段,只跳动那个字段”的局部响应式快感。

5. 总结

如果你习惯 Vue,你会觉得 TanStack Form 的 field.state 非常亲切;如果你习惯 React,你会觉得 TanStack Form 解决了 React 处理大表单时的性能噩梦。

1. 为什么需要 Reactivity?(痛点分析)

在没有响应式机制的年代(比如原生 JS 或早期 jQuery),更新界面是极其繁琐的:

  1. 手动追踪:你需要记住数据变了之后,哪些 HTML 元素需要改。
  2. 手动操作 DOM:你需要写 document.getElementById(...).innerText = newValue
  3. 容易出错:一旦漏掉一个地方,就会出现“数据改了,界面没变”的 Bug(数据不一致)。
  4. 性能瓶颈:频繁的手动 DOM 操作非常消耗浏览器性能。

2. Reactivity 的核心价值

响应式机制解决了上述痛点,带来了以下三个核心优势:

A. 声明式编程 (Declarative Programming)

你只需要描述 UI “长什么样”(基于当前数据),而不需要告诉程序 “怎么去做”(更新步骤)。

B. 细粒度更新 (Fine-grained Updates)

这是 TanStack 系列最引以为傲的地方。优秀的响应式系统能精准定位到“哪一个组件、哪一行文字”需要变,而不是暴力地刷新整个页面。

C. 单一事实来源 (Single Source of Truth)

数据只存在一个地方,所有引用它的组件都会同步变化。这保证了复杂应用中状态的绝对一致性。

3. TanStack Form 里的 Reactivity 是如何工作的?

TanStack Form 采用的是 “发布-订阅”(Pub/Sub) 模式的响应式。

  1. Store(存储):表单的所有状态(Values, Meta, Errors)都存在一个内部的“商店”里。
  2. Subscription(订阅):当你使用 form.Fieldform.Subscribe 时,实际上是在告诉商店:“我对 firstName 这个字段感兴趣,它变了请通知我。”
  3. Notification(通知):当用户输入字母,商店更新数据,并只给订阅了该字段的组件发送“重绘”信号。

tanstack form里面实现Reactivity的两种方法

参考文档:https://tanstack.com/form/latest/docs/framework/react/guides/reactivity

useStore

The useStore hook is perfect when you need to access form values within the logic of your component.

如果是在逻辑代码里面需要访问values,那么就使用useState。

image-20251227185111753

可以看到,两个变量都有响应式了。

<form.Subscribe />

The form.Subscribe component is best suited when you need to react to something within the UI of your component. For example, showing or hiding ui based on the value of a form field.

如果是在UI代码里面需要访问values,就使用<form.Subscribe />

image-20251227185528755

 

可以看到,如果想在UI里面订阅响应式,那么就使用<form.Subscribe />。这个例子就是订阅了firstName,只有firstName有值时,才会显示lastName输入框。

Listeners

@tanstack/react-form(以及更底层的 @tanstack/form-core)中,Listeners(监听器) 是一种允许你在表单状态发生特定变化时,执行副作用(Side Effects)的机制。

如果说 Reactivity(响应式) 是为了更新 UI,那么 Listeners 就是为了触发 逻辑

1. 什么是 Listeners?

Listeners 是附加在表单或字段上的回调函数。它们不直接参与 UI 的渲染,而是在表单的生命周期事件发生时被“通知”。

在 TanStack Form 中,你最常接触到的 Listener 实际上是各种以 on 开头的配置项,例如 onChangeonBluronMount 等。

2. Listeners 的核心作用

A. 联动逻辑(Field Interdependence)

当一个字段的值改变时,去修改另一个字段的值。这是 Listeners 最强大的舞台。

B. 实时保存(Auto-save)

你不想等用户点提交按钮,而是想在用户每输入一个字或离开输入框时就同步到后台。

C. 数据格式化与规范化

在数据存入 Store 之前进行转换。

D. 外部状态同步

将表单内部的状态同步到表单外部(如 Redux、Zustand 或 URL 查询参数中)。

3. 如何使用 Listeners?

在 TanStack Form 中,你可以在两个层级设置监听器:

在字段层级 (Field Level)

这是最常用的方式,用于处理特定字段的逻辑。

在表单层级 (Form Level)

用于处理影响全局的逻辑。

4. Listeners vs. Reactivity 的区别

很多初学者会混淆这两者,它们的本质区别在于:你是想改变“长相”还是想执行“动作”?

维度Reactivity (响应式)Listeners (监听器)
关注点UI 状态行为逻辑
触发结果重新渲染组件 (Re-render)执行一段代码 (Function Call)
典型场景显示错误文字、禁用按钮联动修改其他字段、发送埋点、API 请求
实现方式field.state.value / useStoreonChange / onBlur / onMount

老师案例

<form.Subscribe />例子中,其实有一个问题,就是firstName没值时,lastName虽然可以隐藏,但是lastName里面的值并没有清空:

此时可以定义listeners。

image-20251227190929493

可以看到,lastName输入框隐藏之后,值也被清空了。

同样,可以定义全局的listeners:

Objects & Arrays

Objects(嵌套对象)

将对象里面的变量绑定到form.Field上面,使用点路径(Dot Notation)访问。其余的部分与string、number、boolean这些一致。

可以看到,其实form.Field里面的代码有很多相似的地方,重点就是name属性。

Arrays

基本使用方法是,在form.Field的name属性上,绑定数组的变量名。然后指定mode="array",接着使用field.state.value.map来渲染数组。

<form.Field name="people" mode="array"></form.Field>

然后使用field.pushValuefield.removeValue来新增或者删除数组里面的元素。

数组里面的对象,应该怎么渲染呢?如果有交互,还是使用form.Field来渲染,注意要指定key属性,并且name的值是name={people[${i}].name}这样写,用到了数组的索引。

案例

结构还是很简单的,就是可读性不好,嵌套很多。结合UI看就懂了。可以看到,功能正常。

Field Level Validations

Field Validation(字段校验)

1. 校验的触发时机 (Validation Events)

TanStack Form 允许你精准控制校验在什么时候发生。主要有三个核心时机:

2. 基础校验示例

你可以直接在 form.Fieldvalidators 属性中定义这些逻辑:

3. 异步校验 (Async Validation)

这是处理“检查用户名是否已存在”等场景的神器。你可以将校验函数标记为 async,TanStack Form 会自动处理挂起状态。

4. 配合 Zod 进行模式校验 (Recommended)

手写校验逻辑很累且容易出错。TanStack Form 提供了 Standard Schema Adapter,让你能直接使用 Zod 这种强大的库。

5. 校验状态的传递与 UI 反馈

当校验发生时,field.state.meta 会发生变化,你可以利用这些状态提升体验:

状态属性作用
errors存储当前所有的错误信息数组。
errorMap键值对形式的错误,方便针对特定事件(如 onBlur)取值。
isValidating布尔值,表示异步校验是否正在运行。
isValid该字段当前是否合法。

6. 进阶:跨字段校验

有时候 A 字段的合法性取决于 B 字段(比如“确认密码”必须等于“密码”)。

总结

  1. 同步用 onChange / onBlur:处理简单的逻辑。
  2. 异步用 onChangeAsync + asyncDebounceMs:处理 API 校验。
  3. 模式用 Zod:保持代码整洁和强类型。
  4. UI 反馈看 meta:利用 isValidatingisTouched 优化用户体验。

老师案例

image-20251227211019082

可以看到,当firstName长度<=2时,就会报错。当然也可以设置为onBlur或者onSubmit时校验,看需求。

Form Level Validations

在 TanStack Form 中,Form Validation(表单级校验) 与字段级校验相辅相成。如果说字段级校验关注的是“局部细节”(如邮箱格式是否正确),那么表单级校验关注的是“整体合规性”

它通常用于处理跨字段逻辑(如两次密码一致性)以及提交前的最终关卡

1. 表单级校验的三个核心位置

你可以在 useForm 的配置中直接定义表单级的校验逻辑:

2. 同步校验示例:跨字段联动

最典型的场景是“密码”与“确认密码”的匹配。虽然可以写在字段级,但在表单级处理逻辑会更清晰。

field错误展示

表单级校验产生的错误会存储在 form.state.meta.errors 中,所以在field里面是不能直接获取到的,那么需要使用useStore来订阅errors

返回指定field的错误信息

参考:https://tanstack.com/form/latest/docs/framework/react/guides/validation#setting-field-level-errors-from-the-forms-validators

image-20251227215249109

3. 全局错误展示

表单级校验产生的错误会存储在 form.state.meta.errors 中。你可以将其显示在表单的顶部或底部。

4. 配合 Zod 进行整表校验 (Schema Validation)

这是目前最推荐的做法。通过定义一个完整的 Zod Schema,你可以一次性完成字段级和表单级的所有逻辑。

5. 表单级校验 vs 字段级校验:如何选择?

场景推荐方式理由
基础格式 (如 Email, 必填)Field Validation性能更好,报错直接关联到输入框,用户体验更直观。
逻辑依赖 (如 A 依赖 B)Form Validation在一个地方访问所有数据 value 更容易编写比较逻辑。
业务一致性 (如日期区间)Form Validation方便处理涉及多个字段的联合错误。

6. 校验状态对提交的影响

表单级校验会直接影响 form.state.meta.canSubmit 属性:

  1. 如果有字段错误 canSubmit: false
  2. 如果有表单级错误 canSubmit: false
  3. 如果正在校验 (isValidating) canSubmit: false

这就是为什么你只需要订阅 canSubmit 就能完美控制提交按钮的禁用状态。

阻止invalidate表单的提交

使用form.Subscribe订阅canSubmit和isSubmitting状态,做相应的逻辑、样式。

总结

老师案例

可以看到,全局errors的信息显示成功了。

Async Validations

文档讲解的很清楚了。可以直接参考:https://tanstack.com/form/latest/docs/framework/react/guides/validation#asynchronous-functional-validation

在表单开发中,异步校验(Async Validation) 是处理“需要服务器验证”逻辑的核心,比如检查用户名是否被占用、验证优惠券代码或实时校验银行卡号。

TanStack Form 对异步校验的处理非常优雅,它内置了防抖(Debounce)竞态检查挂起状态(Pending States)的管理。

1. 核心参数:onChangeAsyncasyncDebounceMs

要实现异步校验,你主要会用到这两个属性:

2. 基础代码实现

以下是一个模拟“检查用户名是否重复”的典型例子:

3. 异步校验的状态流转

当异步校验开始时,field.state.meta 会发生以下变化:

  1. 开始校验isValidating 变为 true

  2. 正在进行:此时 canSubmit 会自动变为 false(防止用户在校验完成前提交)。

  3. 结果返回

    • 如果有错:errors 数组被填充,isValid 变为 false
    • 如果没错:errors 为空,isValid 变为 true
  4. 结束校验isValidating 回到 false

4. 为什么 TanStack Form 的异步校验更安全?

在原生开发或简单的 React 开发中,异步校验常遇到竞态问题(Race Conditions)

比如用户先输了 "abc"(请求 A 慢),接着输了 "abcd"(请求 B 快)。如果 A 晚于 B 返回,界面可能会显示 A 的旧报错。

TanStack Form 的优势:

5. 提交时的行为 (onSubmitAsync)

除了 onChangeAsync,你还可以在表单级设置 onSubmitAsync。 即使前端所有校验都通过了,在点击提交瞬间,你可以再次发起一次全局的异步检查:

自带的防抖功能

Built-in Debouncing

image-20251228095603425

asyncDebounceMs属性,会让每一个async请求防抖,validators里面的所有async请求。

也可以单独设置validators里面的防抖属性:

image-20251228095741711

如果设置了onChangeAsyncDebounceMs,那么会覆盖asyncDebounceMs

6. 最佳实践建议

  1. 先同步后异步:在 validators 中先写 onChange(同步),再写 onChangeAsync。只有同步校验(如非空判断、长度判断)通过后,才会触发异步校验,这样可以节省大量的网络请求。
  2. 合理设置 Debounce:通常 300ms - 800ms 是用户体验最好的区间。
  3. 反馈 UI:一定要利用 isValidating 显示一个微小的 Spinner 或提示文本,否则用户会觉得输入框“没反应”。

老师案例

可以看到,输入时触发了async校验。而且因为有防抖,所以校验不会很频繁。

Dynamic Validations

参考文档:https://tanstack.com/form/latest/docs/framework/react/guides/dynamic-validation

它主要解决的问题是: 在表单提交前(或第一次提交后)可以“延迟/改变”校验规则,最经典的场景就是:

这个功能是 TanStack Form 独有的设计,其他表单库(如 React Hook Form)通常是通过 reValidateMode 或手动控制来近似实现,而 TanStack Form 通过 onDynamic + revalidateLogic 提供了原生支持。

Dynamic Validation 的核心机制

  1. 必须搭配 validationLogic: revalidateLogic() 使用 如果不加这个,onDynamic 永远不会被调用。

  2. onDynamic 校验函数

    • 可以定义在 form 级别(validators.onDynamic)
    • 也可以定义在 field 级别(validators.onDynamic)
    • 返回值是一个 错误对象({ fieldName: "error message" })或 undefined
    • 支持同步 + 异步(onDynamicAsync + debounce)
  3. revalidateLogic 的配置(控制校验时机)

    常见组合:

    • mode: 'submit' → modeAfterSubmission: 'change' / 'blur'
    • mode: 'change' → modeAfterSubmission: 'blur'

实际代码示例

1、最经典用法:提交后才开始强制校验

2、字段级别的 dynamic validation

3、异步 + debounce 的 dynamic 校验

4、错误显示方式

老师案例

可以看到,第一次提交表单的时候,触发submit校验,后续的会触发blur校验。

Linked Fields

Linked Fields主要是如何让两个(或多个)表单字段之间建立联动关系,确保当其中一个字段变化时,另一个字段的校验逻辑能够及时重新执行,从而保持数据一致性和实时校验准确性。

这是 TanStack Form 处理字段间相互依赖场景的核心技巧,尤其适合密码确认、起始/结束日期比较、地址联动等常见需求。

核心问题(为什么需要 Linked Fields)

当两个字段相互依赖时(例如 passwordconfirm_password),如果用户先改了 confirm_password,再改了 password,普通的校验方式会导致问题:

解决方案:使用 onChangeListenTo / onBlurListenTo在依赖字段(被影响的那个)的 validators 配置中,添加监听:

关键点:

典型代码模式(密码确认示例)

其他重要特性

配置项作用使用时机建议
onChangeListenTo监听字段每次变化时触发校验实时反馈强(推荐大多数场景)
onBlurListenTo只在监听字段失去焦点时触发校验减少不必要的校验,性能更好
fieldApi.form.getFieldValue(fieldName)在校验函数中安全获取其他字段最新值几乎所有联动场景必用

常见应用场景

最佳实践小结

老师案例

可以看到,当password改变的时候,confirmPassword里面会触发校验。

Validating with ZOD

在 TanStack Form 中使用外部 schema 校验库(如 Zod、Yup、Valibot、ArkType 等)来定义表单的整体校验规则,而不是手动写一堆 onChange / onBlur 函数。

核心思想:把校验逻辑集中到 schema 定义里,让 TanStack Form 自动解析 schema 并应用到字段级别和表单级别。

1、支持的 Schema 库(官方适配器)

TanStack Form 提供了开箱即用的适配器,目前支持以下库:

Schema 库适配器包名导入方式成熟度/推荐度
Zod@tanstack/zod-form-adapterimport { zodValidator } from '@tanstack/zod-form-adapter'★★★★★(最推荐)
Yup@tanstack/yup-form-adapteryupValidator★★★★
Valibot@tanstack/valibot-form-adaptervalibotValidator★★★★(轻量)
ArkType@tanstack/arktype-form-adapterarktypeValidator★★★
Custom自己实现自定义 validatorAdapter灵活但麻烦

Zod 是目前最受欢迎的选择,因为类型推导最强、生态最好。

2、基本使用模式(以 Zod 为例)

formSchema定义之后,就不需要写ts类型了,只需要使用z.infer<typeof formSchema>,就可以推导出类型。非常方便。

将整个form都绑定到onChange上面,当一个field校验不通过时,会自动校验其余的field,但很可能其余的field都没有开始交互,这样用户体验不好。

可以这样显示错误信息,添加一个field.state.meta.isDirty判断:

 

3、字段级别的 schema 使用(推荐)

4、高级用法要点

5、官方推荐的组合策略

场景推荐做法
简单表单字段级 onChange: schema.shape.xxx
复杂表单 + 跨字段校验form 级别 onChange: schema + refine
提交前宽松 / 提交后严格配合 revalidateLogic + onDynamic 使用
需要类型推导优先 Zod(配合 z.infer<typeof formSchema>

老师案例

可以看到,校验都触发了,而且写规则很简单。

Validating with ZOD(Refine and Async)

在 Zod 中使用 refine 和 refineAsync 是处理复杂/跨字段/异步校验的最常用方式,尤其在 TanStack Form 中结合使用时非常强大。

下面是目前(2025 年底)最实用、最常见的几种用法和写法,涵盖同步 refine、异步 refine 和在 TanStack Form 中的集成方式。

1、基础:同步 refine(跨字段校验)

关键点:

2、多个 refine(推荐写法:链式调用)

3、异步 refine(refineAsync)

最常见场景:检查用户名/邮箱是否已被占用、验证码是否正确等。

重要注意:

注意:如果涉及到linked field校验,那么refine写在formSchema上比较好,如果是单个的refine校验,也可以写在具体的field标签上。

4、在 TanStack Form 中的完整集成(推荐组合)

快速对比表:refine vs refineAsync vs superRefine

方法同步/异步能拿到什么适合场景性能影响
.refine同步整个表单数据简单跨字段逻辑极低
.refineAsync异步整个表单数据API 检查、数据库唯一性等中等
.superRefine同步ctx + 整个数据非常复杂的自定义逻辑(带 ctx.addIssue)

5、小建议(生产常用模式)

  1. 基础规则 → 用 z.object({...}).shape.xxx
  2. 跨字段简单逻辑 → 用 refine
  3. 需要异步 → 用 refineAsync
  4. 超级复杂 → 用 superRefine + ctx.addIssue
  5. 提交前宽松 → 配合 TanStack Form 的 revalidateLogic + onDynamic

Notes on Form Submission

使用transform对数据进行转换

作用:在校验通过后,对数据进行任意转换,常用于:

重要特性:

案例:后端只需要数组的id,而不是整个对象数组。

使用parse将transform后的数据解析出来

作用:启动校验、拿到安全数据。

如果有多个操作按钮,想要触发不同的事件,该怎么办?

在 TanStack Form(@tanstack/react-form)中处理一个表单里有多个 submit 按钮的场景是非常常见的,比如:

核心问题是:如何区分用户点击的是哪个 submit 按钮,并在提交时知道具体意图?推荐的几种主流处理方式

方式实现难度代码清晰度推荐场景备注
1. 隐藏的 intent 字段 + value★☆☆★★★★★最推荐,几乎所有情况都适用官方文档推荐,简单可靠
2. form.handleSubmit + 自定义 onSubmit★★☆★★★★需要更灵活控制适合复杂逻辑

方式1:最推荐 - 使用隐藏的 intent 字段(Hidden Input + value)

这是 TanStack Form 社区和官方最推荐的做法,原理简单:用一个隐藏字段记录当前点击的按钮意图。

优点:

方式2:使用自定义 onClick + form.submit()

可以看到,输出的meta的不同。